﻿using System;
using System.Configuration;
using System.ComponentModel;
using System.Linq;
using System.Data.Linq;
using System.Collections.Generic;
using Xeml.Sampling.Contracts;
using System.Xml.Serialization;
using System.Collections;

namespace Xeml.SQLDataProviders
{

    /// <summary>
    /// The 'factory' class.
    /// You can ask for configurations from this class.
    /// </summary>
    public static class Configuration
    {
        static Configuration()
        {
            if (Xeml.Document.Properties.Settings.Default.DataProvider == null)
            {
                Xeml.Document.Properties.Settings.Default.DataProvider = new UniqueList();
            }
        }

        /// <summary>
        /// Based on an IDataProvider uri, return a dictionary with all associated configurations
        /// </summary>
        /// <param name="uri">IDataProvider Uri</param>
        /// <returns>
        /// key: human readable name for the configuration
        /// value: the configuration
        /// </returns>
        public static Dictionary<string, DataProviderConfiguration> GetDataProviderConfigurations(string uri /*, bool autovivify = true */)
        {
            return Xeml.Document.Properties.Settings.Default.DataProvider.Where(x => x.ProviderUri == uri).ToDictionary(x => x.Name, x => x);
        }

        /// <summary>
        /// Return the configuration with this name.
        /// </summary>
        /// <param name="name">The name unique of the configuration.</param>
        /// <returns>A DataProviderConfiguration object</returns>
        public static DataProviderConfiguration GetConfiguration(string name)
        {
            return Xeml.Document.Properties.Settings.Default.DataProvider.Where(x => x.Name == name).FirstOrDefault();
        }

        /// <summary>
        /// Save the configuration to the user.config settings file. The default name of the configuration will be the 
        /// Name property of the configuration object.
        /// </summary>
        /// <param name="configuration">the Configuration object.</param>
        public static void SaveConfiguration(DataProviderConfiguration configuration)
        {
            SaveConfiguration(configuration, configuration.Name);
        }
        
        /// <summary>
        /// Save the configuration to the user.config settings file.
        /// </summary>
        /// <param name="configuration">The Configuration object</param>
        /// <param name="configurationName">The name of the configuration</param>
        public static void SaveConfiguration(DataProviderConfiguration configuration, string configurationName)
        {
            // replace the current config
            if (Xeml.Document.Properties.Settings.Default.DataProvider.Contains(configurationName))
            {
                Xeml.Document.Properties.Settings.Default.DataProvider.Remove(configurationName);
            }
            Xeml.Document.Properties.Settings.Default.DataProvider.Add(configuration);
            Xeml.Document.Properties.Settings.Default.Save();
        }

        public static void SaveConfigurations(List<DataProviderConfiguration> configs)
        {
            foreach (DataProviderConfiguration config in configs)
            {
                SaveConfiguration(config);
            }
        }

        public static void UpdateConfiguration(DataProviderConfiguration dpConfig, string originalName, string newName)
        {
            Xeml.Document.Properties.Settings.Default.DataProvider.Remove(originalName);
            SaveConfiguration(dpConfig, newName);
        }
    }

    #region Datastructure for the configuration
    // needed to make this into a list, as a dictionary is not serialize by default in C#
    [Serializable]
    public class UniqueList : IList<DataProviderConfiguration>
    {
        [XmlElement()]
        public List<DataProviderConfiguration> list;

        public UniqueList():base()
        {
            list = new List<DataProviderConfiguration>();
        }

        public int IndexOf(DataProviderConfiguration item)
        {
            return this.IndexOf(item.Name);
        }

        public int IndexOf(string name)
        {
            for (int i = 0; i < this.Count; i++)
            {
                if (this[i].Name == name)
                {
                    return i;
                }
            }
            return -1;
        }

        public void Insert(int index, DataProviderConfiguration item)
        {
            list.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            list.RemoveAt(index);
        }

        public DataProviderConfiguration this[int index]
        {
            get
            {
                return list[index];
            }
            set
            {
                list[index] = value;
            }
        }

        public DataProviderConfiguration this[string name]
        {
            get
            {
                return list.FirstOrDefault(x => x.Name == name);
            }
            set
            {
                this.Remove(name);
                list.Add(value);
            }
        }

        public void Clear()
        {
            list.Clear();
        }

        public bool Contains(string name)
        {
            return this.IndexOf(name) != -1;
        }

        public bool Contains(DataProviderConfiguration item)
        {
            return this.Contains(item.Name);
        }

        public void CopyTo(DataProviderConfiguration[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        public int Count
        {
            get {
                return list.Count;
            }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(string name)
        {
            int index = this.IndexOf(name);
            if (index != -1)
            {
                this.RemoveAt(index);
                return true;
            }
            return false;
        }

        public bool Remove(DataProviderConfiguration item)
        {
            return this.Remove(item.Name);
        }

        public IEnumerator<DataProviderConfiguration> GetEnumerator()
        {
            return list.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return list.GetEnumerator();
        }

        public void Add(DataProviderConfiguration item)
        {
            if (this.Contains(item))
            {
                throw new DuplicateKeyException(item);
            }

            list.Add(item);
        }
    }

    [Serializable]
    public class DataProviderConfiguration: INotifyPropertyChanged
    {
        #region constructors
        public DataProviderConfiguration() { }

        public DataProviderConfiguration(String uri): base()
        {
            this.ProviderUri = uri;
            this.Name = String.Empty;
            this.ConnectionString = String.Empty;
            this.Database = String.Empty;
            this.Password = String.Empty;
            this.Server = String.Empty;
            this.Username = String.Empty;
            this.WindowsAuthentication = false;
            this.SampleMappingQueries = new MappingQueryConfigurations();
        }

        public DataProviderConfiguration(DataProviderConfiguration that): base()
        {
            this.ProviderUri = that.ProviderUri;
            this.Name = that.Name;
            this.ConnectionString = that.ConnectionString;
            this.Database = that.Database;
            this.Password = that.Password;
            this.Server = that.Server;
            this.Username = that.Username;
            this.WindowsAuthentication = that.WindowsAuthentication;
            this.SampleMappingQueries = that.SampleMappingQueries;
        }
        #endregion

        [Browsable(false)]
        public String ConnectionString
        {
            get;
            set;
        }

        private string _username;
        [Category("common")]
        [DisplayName("Username")]
        [Description("The username used to login.")]
        [Browsable(true)]
        [DefaultValue("")]
        public String Username
        {
            get {
                return _username;
            }
            set {
                _username = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("username"));
                }
            }
        }

        private string _password;
        [Category("common")]
        [DisplayName("Password")]
        [Description("The password used to login.")]
        [Browsable(true)]
        [PasswordPropertyText(true)]
        public String Password { 
            get {
                return _password;
            }
            set {
                _password = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("password"));
                }
            }
        }

        private string _server;
        [Category("common")]
        [DisplayName("Server name")]
        [Description("The database server name.")]
        [Browsable(true)]
        public String Server {
            get {
                return _server;
            }
            set {
                _server = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("server"));
                }
            }
        }
 
        private uint _port;
        [Category("common")]
        [DisplayName("Port")]
        [Description("The database port.")]
        [Browsable(true)]
        public uint Port {
            get {
                return _port;
            }
            set {
                _port = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("port"));
                }
            }
        }

        private string _database;
        [Category("common")]
        [DisplayName("Database name")]
        [Description("The name of the datebase.")]
        [Browsable(true)]
        public String Database {
            get {
                return _database;
            }
            set {
                _database = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("database"));
                }
            }
        }

        private string _providerUri;
        /// <summary>
        /// FQN of the type
        /// </summary>
        [Category("common")]
        [DisplayName("DataProvider Uri")]
        [Description("A unique identifier for the DataProvider used.")]
        [Browsable(false)]
        public String ProviderUri {
            get {
                return _providerUri;
            }
            set {
                _providerUri = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("providerUri"));
                }
            }
        }

        private string _name;
        /// <summary>
        /// A human readable name
        /// </summary>
        [Category("common")]
        [DisplayName("Human readable name")]
        [Description("A short descriptive name of this configuration.")]
        [Browsable(true)]
        public String Name {
            get {
                return _name;
            }
            set {
                _name = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("name"));
                }
            }
        }

        private bool _windowsAuthentication;
        /// <summary>
        /// Whether or not we can skip authentication as windows just takes care of everything
        /// </summary>
        [Category("common")]
        [DisplayName("Windows Authentication")]
        [Description("Authentication will be handled by windows. Username and password will be ignored.")]
        [Browsable(true)]
        public Boolean WindowsAuthentication {
            get {
                return _windowsAuthentication;
            }
            set {
                _windowsAuthentication = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("windowsAuthentication"));
                }
            }
        }

        private MappingQueryConfigurations _sampleMappingQueries;
        /// <summary>
        /// A list of queries to map samples to chromatograms
        /// </summary>
        [Category("SQL Queries")]
        [DisplayName("Sample Mapping Queries")]
        [Description("A list of queries to map samples to chromatograms")]
        [Browsable(true)]
        [TypeConverter(typeof(MappingQueryConfigurationsConvertor))]
        public MappingQueryConfigurations SampleMappingQueries {
            get {
                return _sampleMappingQueries;
            }
            set {
                _sampleMappingQueries = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("SampleMappingQueries"));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
    #endregion

    #region SQL Queries configuration
    /// <summary>
    /// A more complex property: a collection. (uh-la-la)
    /// See: http://www.codeproject.com/Articles/4448/Customized-display-of-collection-data-in-a-Propert
    /// </summary>
    public class MappingQueryConfigurations: CollectionBase, ICustomTypeDescriptor
    {

        public void Add(MappingQuery item)
        {
            if (this.Contains(item))
            {
                throw new DuplicateKeyException(item);
            }

            this.List.Add(item);
        }

        public bool Remove(MappingQuery item)
        {
            if (this.Contains(item))
            {
                this.List.RemoveAt(this.IndexOf(item.Name));
                return true;
            }
            return false;
        }

        public MappingQuery this[int index]
        {
            get
            {
                return (MappingQuery)this.List[index];
            }
        }

        public bool Contains(MappingQuery item)
        {
            return this.List.IndexOf(item) != -1;
        }

        public int IndexOf(string name)
        {
            for (int i = 0; i < this.Count; i++)
            {
                if (((MappingQuery)this.List[i]).Name == name)
                {
                    return i;
                }
            }
            return -1;
        }

        public AttributeCollection GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }

        public string GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

        public string GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }

        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        public PropertyDescriptor GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this, true);
        }

        public object GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            return GetProperties();
        }

        public PropertyDescriptorCollection GetProperties()
        {
            PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);

            for (int i = 0; i < this.List.Count; i++)
            {
                MappingQueryPropertyDescriptor mqpd = new MappingQueryPropertyDescriptor(this, i);
                pds.Add(mqpd);
            }

            return pds;
        }

        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }
    }

    public class MappingQueryPropertyDescriptor : PropertyDescriptor
    {
        private MappingQueryConfigurations _collection = null;
        private int _index = -1;

        public MappingQueryPropertyDescriptor(MappingQueryConfigurations collection, int index)
            : base("#" + index.ToString(), null)
        {
            this._collection = collection;
            this._index = index;
        }

        public override string DisplayName
        {
            get
            {
                return _collection[_index].Name;
            }
        }

        public override string Description
        {
            get
            {
                return "Describe the queries to unique map a sample to a chromatogram." +
                    "e.g. if a sample uniquely maps to a chromatogram based on the experiment and the chromatogram name then one needs to provide two queries: one for all experiments and one for all chromatograms (of that experiment).";
            }
        }

        public override bool CanResetValue(object component)
        {
            return true;
        }

        public override Type ComponentType
        {
            get {
                return this._collection.GetType();
            }
        }

        public override object GetValue(object component)
        {
            return this._collection[_index];
        }

        public override bool IsReadOnly
        {
            get { return true; }
        }

        public override Type PropertyType
        {
            get { return this._collection[_index].GetType(); }
        }

        public override void ResetValue(object component) { }

        public override void SetValue(object component, object value)
        {
            //this._collection[index] = value;
        }

        public override bool ShouldSerializeValue(object component)
        {
            return true;
        }
    }

    [Serializable]
    [TypeConverter(typeof(MappingQueryConvertor))]
    public class MappingQuery
    {
        public MappingQuery() { }

        public MappingQuery(String Key, String Value)
        {
            this.Name = Key;
            this.Value = Value;
        }

        [Category("Required")]
        [DisplayName("Unique name")]
        [Description("A unique name for this mapping condition.")]
        [Browsable(true)]
        public String Name
        {
            get;
            set;
        }

        [Category("Required")]
        [DisplayName("Query")]
        [Description("Query to select values of this mapping condition.")]
        [Browsable(true)]
        public String Value
        {
            get;
            set;
        }
    }
    #endregion

    #region convertors to change the propertyGrid behaviour
    internal class MappingQueryConfigurationsConvertor : ExpandableObjectConverter
    {
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is MappingQueryConfigurations)
            {
                return String.Empty;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    internal class MappingQueryConvertor : ExpandableObjectConverter
    {
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is MappingQuery)
            {
                return ((MappingQuery)value).Value;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
    #endregion
}
